#pragma once

#include <cuda_runtime.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iterator>
#include <cstdio>

// cuda kernel has less support for class, use struct instead
struct Dsp
{
    int v;
    int k1;
    int k2;
    int e;
    int lambda;
    bool valid;

    unsigned char *k1Set;
    unsigned char *k2Set;
    unsigned char *diffTable;
};

std::ostream &operator<<(std::ostream &cout, const Dsp &dsp)
{
    // todo change unsigned char to int to print out
    cout << "dsp(" << dsp.v << "," << dsp.k1 << "," << dsp.k2 << "," << dsp.e << "," << dsp.lambda << ")=[[" << static_cast<int>(dsp.k1Set[0]);
    for (int i = 1; i < dsp.k1; i++)
    {
        cout << ", " << static_cast<int>(dsp.k1Set[i]);
    }

    cout << "],[" << static_cast<int>(dsp.k2Set[0]);
    for (int i = 1; i < dsp.k2; i++)
    {
        cout << ", " << static_cast<int>(dsp.k2Set[i]);
    }
    cout << "]]";
    return cout;
}

// compute difference table of dsp, check if this dsp is invalid
// return true if dsp is valid, false otherwise
static __device__ __host__ bool initializeDsp(Dsp *dsp)
{
    // memset dsp->diffTable to 0
    for (int i = 0; i < (dsp->k1 * dsp->k2); i++)
    {
        dsp->diffTable[i] = 0;
    }

    for (int i = 0; i < dsp->k1; i++)
    {
        for (int j = 0; j < dsp->k2; j++)
        {
            unsigned char idx = (dsp->k1Set[i] - dsp->k2Set[j] + dsp->v) % dsp->v;
            dsp->diffTable[idx] = dsp->diffTable[idx] + 1;
        }
    }

    // determine whether dsp is valid by counting diffTable
    int idx = 1;
    while (dsp->diffTable[idx] == dsp->lambda && idx != (dsp->k1 * dsp->k2))
    {
        idx++;
    }

    dsp->valid = idx == (dsp->k1 * dsp->k2) ? true : false;
    return dsp->valid;
}

static bool isDsp(const std::vector<int> &arr1, const std::vector<int> &arr2, int v, int lambda)
{
    int k1 = arr1.size();
    int k2 = arr2.size();
    std::vector<int> diff(k1 * k2, 0);
#ifndef NDEBUG
    printf("====>>>>%s \ndiff=", __func__);
    std::copy(diff.begin(), diff.end(), std::ostream_iterator<int>(std::cout, " "));
    printf("\n<<<<====\n");
#endif
    for (auto i : arr1)
    {
        for (auto j : arr2)
        {
            int idx = (i - j + v) % v;
            diff[idx] = diff[idx] + 1;
        }
    }
#ifndef NDEBUG
    printf("====>>>>%s \ndiff=", __func__);
    std::copy(diff.begin(), diff.end(), std::ostream_iterator<int>(std::cout, " "));
    printf("\n<<<<====\n");
#endif
    for (int i = 1; i < k1 * k2; i++)
    {
        if (diff[i] != lambda)
            return false;
    }

    return true;
}

static std::vector<Dsp> readDsp(const char *filename)
{
    // convert filename to args
    int v, k1, k2, e, lambda;
    sscanf(filename, "dsp-%d-%d-%d-%d-%d.txt", &v, &k1, &k2, &e, &lambda);
#ifndef NDEBUG
    printf("converted args: v=%d, k1=%d, k2=%d, e=%d, lambda=%d\n", v, k1, k2, e, lambda);
#endif

    std::ifstream infile(filename); // open input file
    std::string line;               // declare a string variable to hold each line of input
    // prepare output
    std::vector<Dsp> rs;

    while (std::getline(infile, line))
    { // read each line of input
        Dsp dsp;
        dsp.v = v;
        dsp.k1 = k1;
        dsp.k2 = k2;
        dsp.e = e;
        dsp.lambda = lambda;
        dsp.k1Set = new unsigned char[dsp.k1];
        dsp.k2Set = new unsigned char[dsp.k2];
        dsp.diffTable = new unsigned char[dsp.k1 * dsp.k2];
        std::stringstream ss(line); // create a stringstream from the current line
        char ch;                    // declare a character variable to hold the commas and brackets

        // read the first array from the stringstream
        ss >> ch; // read the opening bracket
        ss >> ch; // read the opening bracket
        int x;
        int idx = 0;
        while (ss >> x)
        { // read the elements of the array
            dsp.k1Set[idx++] = x;
            ss >> ch; // read the comma or closing bracket
            if (ch == ']')
                break; // exit the loop if the closing bracket is read
        }

        idx = 0;
        // read the second array from the stringstream
        ss >> ch; // read the comma
        ss >> ch; // read the opening bracket
        while (ss >> x)
        { // read the elements of the array
            dsp.k2Set[idx++] = x;
            ss >> ch; // read the comma or closing bracket
            if (ch == ']')
                break; // exit the loop if the closing bracket is read
        }

        bool valid = initializeDsp(&dsp);
        if (valid)
        {
            rs.push_back(dsp);
        }
    }

    return rs;
}